/**
 * sojamo.osc is a processing and java library for the
 * open sound control protocol, OSC.
 *
 *  2006 by Andreas Schlegel
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307 USA
 *
 * @author Andreas Schlegel (http://www.sojamo.de)
 *
 */

package sojamo.osc;

import java.lang.reflect.Method;

/**
 * 
 * @invisible
 */
public class OSCPlug {

    private boolean _isValid = true;

    private String _myTypetag = "";

    private String _myAddrPattern = "";

    private String _myPattern = "";

    private String _myMethodName;

    private Object _myObject;

    public Method method = null;

    private int _myChecker = 0;

    protected boolean isArray = false;

    private final static int CHECK_ADDRPATTERN_TYPETAG = 0;

    private final static int CHECK_ADDRPATTERN = 1;

    private final static int CHECK_TYPETAG = 2;

    public void plug(final Object theObject, final String theMethodName,
	    final String theAddrPattern) {
	_myObject = theObject;
	_myMethodName = theMethodName;
	_myAddrPattern = theAddrPattern;
	_myChecker = CHECK_ADDRPATTERN_TYPETAG;
	if (_myMethodName != null && _myMethodName.length() > 0) {
	    Class myClass = theObject.getClass();
	    Class[] myParams = null;
	    Method[] myMethods = myClass.getMethods();
	    _myTypetag = "";
	    for (int i = 0; i < myMethods.length; i++) {
		if ((myMethods[i].getName()).equals(_myMethodName)) {
		    myParams = myMethods[i].getParameterTypes();
		    for (int j = 0; j < myParams.length; j++) {
			_myTypetag += checkType(myParams[j].getName());
		    }
		    break;
		}
	    }
	    if (myParams != null) {
		makeMethod(theObject.getClass(), myParams);
	    } else {
		OSC.printWarning("OscPlug.plug()",
			"no arguments found for method " + 
			_myMethodName);
	    }
	}
    }

    public void plug(final Object theObject, final String theMethodName,
	    final String theAddrPattern, final String theTypetag) {
	_myObject = theObject;
	_myMethodName = theMethodName;
	_myAddrPattern = theAddrPattern;
	_myTypetag = theTypetag;
	_myChecker = CHECK_ADDRPATTERN_TYPETAG;

	if (_myMethodName != null && _myMethodName.length() > 0) {
	    int tLen = _myTypetag.length();
	    Class[] myParams;
	    if (tLen > 0) {
		myParams = getArgs(_myTypetag);
	    } else {
		myParams = null;
	    }

	    if (_isValid) {
		makeMethod(theObject.getClass(), myParams);
	    }
	}
    }

    public Object getObject() {
	return _myObject;
    }

    private void makeMethod(final Class theObjectsClass, final Class[] theClass) {
	try {
	    method = theObjectsClass.getDeclaredMethod(_myMethodName, theClass);
	    _myPattern = _myAddrPattern + _myTypetag;
	    method.setAccessible(true);
	    OSC.printProcess("OscPlug", 
		    "plugging " 
		    + theObjectsClass + " | "
		    + "addrPattern:" + _myAddrPattern
		    + " typetag:" + _myTypetag + 
		    " method:" + _myMethodName);

	} catch (Exception e) {
	    final Class theObjecsSuperClass = theObjectsClass.getSuperclass();
	    if (theObjecsSuperClass.equals(Object.class)) {
		if (theObjectsClass.getName().equals("java.awt.Component") == false) { // applet
											// fix.
		    OSC.printError("OscPlug", 
			    "method "
			    + theObjectsClass.getName()
			    + " does not exist in your code.");
		}
	    } else {
		makeMethod(theObjecsSuperClass, theClass);
	    }
	}
	return;
    }

    public boolean checkMethod(final OSCMessage theOscMessage,
	    final boolean isArray) {
	String myTypetag;
	/*
	 * if theFlag is true and the arguments of theOscmessage can be
	 * represented as an array of the same type, then only fetch the first
	 * character of the typetag, otherwise use the full typetag.
	 */
	if (isArray) {
	    myTypetag = "" + theOscMessage.typetag().charAt(0);
	} else {
	    myTypetag = theOscMessage.typetag();
	}
	switch (_myChecker) {
	case (CHECK_ADDRPATTERN_TYPETAG):
	    String thePattern = theOscMessage.addrPattern() + myTypetag;
	    return thePattern.equals(_myPattern);
	case (CHECK_ADDRPATTERN):
	    return (theOscMessage.addrPattern().equals(_myAddrPattern));
	case (CHECK_TYPETAG):
	    return (myTypetag.equals(_myTypetag));
	default:
	    return false;
	}
    }

    public Method getMethod() {
	return method;
    }

    public String checkType(final String theName) {
	if (theName.equals("int")) {
	    return "i";
	} else if (theName.equals("float")) {
	    return "f";
	} else if (theName.equals("java.lang.String")) {
	    return "s";
	} else if (theName.equals("[Ljava.lang.String;")) {
	    isArray = true;
	    return "s";
	}

	else if (theName.equals("char")) {
	    return "c";
	} else if (theName.equals("[B")) {
	    return "b";
	} else if (theName.equals("[F")) {
	    isArray = true;
	    return "f";
	} else if (theName.equals("[I")) {
	    isArray = true;
	    return "i";
	}

	else if (theName.equals("double")) {
	    return "d";
	} else if (theName.equals("boolean")) {
	    return "T";
	} else if (theName.equals("long")) {
	    return "h";
	}
	return "";
    }

    private Class[] getArgs(final String theArgs) {
	char[] tChar = theArgs.toCharArray();
	int tLen = theArgs.length();
	Class[] tClass = new Class[tLen];
	for (int i = 0; i < tLen; i++) {
	    switch (tChar[i]) {
	    case ('i'):
		tClass[i] = (isArray == true) ? int[].class : int.class;
		break;
	    case ('S'):
	    case ('s'):
		tClass[i] = (isArray == true) ? String[].class : String.class;
		break;
	    case ('f'):
		tClass[i] = (isArray == true) ? float[].class : float.class;
		break;
	    case ('d'):
		tClass[i] = double.class;
		break;
	    case ('c'):
		tClass[i] = char.class;
		break;
	    case ('h'):
	    case ('l'):
		tClass[i] = long.class;
		break;
	    case ('T'):
		tClass[i] = boolean.class;
		break;
	    case ('F'):
		tClass[i] = boolean.class;
		break;
	    case ('b'):
		tClass[i] = byte[].class;
		break;
	    case ('o'):
		_myChecker = CHECK_ADDRPATTERN;
		tClass = new Class[] { Object[].class };
		break;

	    default:
		_isValid = false;
		break;
	    }
	}
	if (!_isValid) {
	    tClass = null;
	    System.out.println("ERROR could't plug method " + _myMethodName);
	}
	return tClass;
    }

}
